#include "g_local.h"
#include "bots_local.h"

// All map related code goes in this file

#define		MAX_MAPS		32
#define		MAX_MAPS_TEXT	16384

int		mapMode = FLAG_CAPTURES;
int		votetotal;
int		maptotal;
vote_t  votes[MAX_MAPS];

int bots_numPlayers()
{
	gentity_t	*player;
	int			n, numplayers;

	numplayers = 0;
	for (n = 0; n < level.maxclients; n++)
	{
		player = &g_entities[n];

		if (!player->client)
			continue;
		if (!player->inuse)
			continue;
		if (player->client->sess.sessionTeam == TEAM_SPECTATOR)
			continue;

		numplayers++;
	}

	return numplayers;
}


void bots_mapVote(int clientNum, char* as_mapname) {
	gentity_t  *ent = g_entities + clientNum;
	int			n, maxvote, numplayers, minvote, vote=0;
	qboolean	lb_found;
	char		ls_nextmap[MAX_STRING_CHARS];


	numplayers = bots_numPlayers();

//	G_LogPrintf( "BOTS: Voted Map Name: %s\n", as_mapname);

	if (!ent->client)
		return;

	lb_found = qfalse;
	for (n=0; n < maptotal; n++) {
		if (Q_strncmp (votes[n].levelname, as_mapname, strlen (as_mapname)) == 0) {
			vote = n;
			lb_found = qtrue;
			break;
		}
	}

	if (!lb_found) {
		BOTS_cp_console(clientNum, "Error selecting a map.");
		return;
	}

	if (ent->client->pers.hasvoted)
		votes[ent->client->pers.castvote].numvotes--;
	else
		votetotal++;

	if (numplayers > 1)
		minvote = (int)(numplayers / 2) + 1;
	else
		minvote = 1;

	votes[vote].numvotes++;
	
	ent->client->pers.castvote = vote;
	ent->client->pers.hasvoted = qtrue;

	if (votetotal >= minvote)
	{
		maxvote = 0;
		for (n=0; n < maptotal; n++)
		{
			if(votes[n].numvotes > maxvote) {
				maxvote = votes[n].numvotes;
				vote = n;
			}
		}

		for (n=0; n < maptotal; n++)
			votes[n].numvotes = 0;

		trap_Cvar_VariableStringBuffer( "nextmap", ls_nextmap, sizeof(ls_nextmap) );

		if ((vote >= MAX_MAPS) || (vote < 1))
			vote = 0;

		if (*ls_nextmap)
			Com_sprintf( level.voteString, sizeof( level.voteString ), "map %s; set nextmap \"%s\"", votes[vote].levelmap, ls_nextmap );
		else
			Com_sprintf( level.voteString, sizeof( level.voteString ), "map %s", votes[vote].levelmap );

			PrintMsg (NULL, "Map %s won with %i vote(s)\n", votes[vote].levelname, maxvote);
		
		trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) );
		BeginIntermission ();
	}
	else
		PrintMsg (NULL, "%i" S_COLOR_WHITE " people have voted.\n\nType 'mapvote' in console to cast a vote.\n", votetotal);
}


void bots_voteSetup(int clientNum)
{
	char*		list = NULL;
	int			i;
	gentity_t*	ent = g_entities + clientNum;
	
	if (ent->client->pers.maplistsent) {
		for (i = 0; i < maptotal; i++)
			list = va("%s\\mvt%i\\%i", list, i, votes[i].numvotes);

		trap_SendServerCommand(clientNum, "mapvote_list_complete");
		return;
	}

	ent->client->pers.maplistsent = qtrue;

	list = va("");

	for (i = 0; i < maptotal; i++) {
		list = va("%s\\map%i\\%s", list, i, votes[i].levelname);
		list = va("%s\\mvt%i\\%i", list, i, votes[i].numvotes);
	}

	trap_SetConfigstring(CS_BOTS_MENU + clientNum, va("size\\%i%s", i, list));

	trap_SendServerCommand(clientNum, "mapvote_list_complete");
}


void bots_clearVotes()
{
	gentity_t	*player;
	int			i;

	votetotal = 0;

	for (i=1; i < MAX_MAPS; i++)
		votes[i].numvotes = 0;

	for (i = 0; i < g_maxclients.integer; i++) {
		player = g_entities + i;

		if (!player->inuse || !player->client)
			continue; 
		player->client->pers.castvote = 0;
	}
}

void bots_parseMaps( char *as_buffer ) {
	int			map;
	char		*ls_token;
	char		ls_mapname[128] = {0};
	qboolean	lb_error;

//	G_LogPrintf( "BOTS: ----------- BEGIN ------------\n");

	lb_error = qfalse;
	map = 0;
	while (1)
	{	
		ls_token = COM_Parse( &as_buffer );
		if ( !ls_token[0] ) {
			break;
		}

		Q_strncpyz( votes[map].levelmap, ls_token, sizeof(votes[map].levelmap) );

//		G_LogPrintf( "BOTS: Map File: %s\n", votes[map].levelmap);

		ls_mapname[0] = 0;
		while ( 1 ) {
			ls_token = COM_Parse( &as_buffer );
			if ( !ls_token[0] ) {
				Com_Printf( "Missing map description in vote.lst file\n" );
				lb_error = qtrue;
				break;
			}

			if ( ls_token[0] == '/') {
				break;
			}

			if (!ls_mapname[0])
				Q_strncpyz( ls_mapname, ls_token, sizeof(ls_mapname) );
			else {
				Q_strcat( ls_mapname, 128, " " );
				Q_strcat( ls_mapname, 128, ls_token );
			}
		}
		
		if ( ls_token[0] != '/' && !lb_error) {
			Com_Printf( "Missing '/' after map description in vote.lst file\n" );
			break;
		}

		Q_strncpyz( votes[map].levelname, ls_mapname, sizeof(votes[map].levelname) );
		
//		G_LogPrintf( "BOTS: Map Name: %s\n", votes[map].levelname);

		map++;
		if (map > MAX_MAPS)
			break;
	}

//	G_LogPrintf( "BOTS: ----------- END ------------\n");

	maptotal = map;
}

void bots_ReadMaps (char *filename) {
	int				li_length;
	char			ls_buffer[MAX_MAPS_TEXT];
	char			ls_filename[128];
	fileHandle_t	lfh_file;

	// Build the filename
	ls_filename[0] = 0;
	Q_strcat( ls_filename, 128, "vote.lst" );
	Q_strncpyz( ls_filename, Q_strlwr( ls_filename ), sizeof(ls_filename) );
	
	// Open the file if it exists
	li_length = trap_FS_FOpenFile( ls_filename, &lfh_file, FS_READ );
	if ( !lfh_file ) {
		G_LogPrintf( "BOTS: No maps found in %s\n", ls_filename);
		return;
	}
	if ( li_length >= MAX_MAPS_TEXT ) {
		G_LogPrintf( "BOTS: Map voting list file too large: %s is %i, max allowed is %i\n", ls_filename, li_length, MAX_MAPS_TEXT );
		trap_FS_FCloseFile( lfh_file );
		return;
	}

	// Read the map data
	trap_FS_Read( ls_buffer, li_length, lfh_file );

	// Close the string off
	ls_buffer[li_length] = 0;

	// Parse and spawn the entities
	bots_parseMaps( ls_buffer ) ;

	// Close the file
	trap_FS_FCloseFile( lfh_file );
}


void bots_initVoting() {
	int		i;

	for (i=1; i < MAX_MAPS; i++) {
		votes[i].levelmap[0] = 0;
		votes[i].levelname[0] = 0;
	}

	bots_ReadMaps("vote.lst");
	bots_clearVotes();
}


void bots_setMode(int ai_mapmode) {
	mapMode = ai_mapmode;
}

int bots_getMode() {
	return mapMode;
}

// Tricky reuse of a variable
// bots_flags for a cappad means: bit 1 set => bots_goal_all, bit 2 set => this instance was conquered
void bots_GoalThink(gentity_t *self)
{
	int conquered = BOTS_conquer_get(self->bots_team);


	if (conquered >= 0) {
		// It's conquered, check if it's still valid
		gentity_t	*soldier = &g_entities[conquered];
		

		if (soldier->bots_class != BOTS_SOLDIER || !soldier->client || !soldier->client->qb_conquerMode)
			BOTS_conquer_clear(self->bots_team);
		else {
			// Be sure to check only 1 pad for distance (if there's more than one for each team)
			// => The one that has been conquered
			if (self->bots_flags&2) {
				float		
					maxDist = 400.0f;
				
				// Check if the distance is ok
				if (soldier->client->pers.classlevel > 1)
					maxDist *= 1.0f + ((float)(soldier->client->pers.classlevel)*0.5f);
				
				if ( Distance(soldier->s.pos.trBase, self->s.pos.trBase) > maxDist)
					BOTS_conquer_clear(self->bots_team);
			}
		}
	}
	else 
		self->bots_flags &= ~2; // Clear that damn bit


	// Is it a "bots_goal_all" ent (alt. teams)?
	if ((self->bots_flags & 1) && (level.time >self->i_dropTime)) {
		if (self->bots_team == TEAM_RED) {
			self->bots_team = TEAM_BLUE;
			self->s.modelindex = G_ModelIndex("models/bots/capture_pad_blue.md3");
		}
		else {
			self->bots_team = TEAM_RED;
			self->s.modelindex = G_ModelIndex("models/bots/capture_pad_red.md3");
		}
		// self->nextthink = level.time + 10000;
		self->i_dropTime = level.time + 10000; // Debounce
	}

	self->nextthink = level.time + FRAMETIME;
}


int Team_TouchOurFlag( gentity_t *ent, gentity_t *other, int team, qboolean ab_cappad );
void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... );

void bots_GoalTouch (gentity_t *ent, gentity_t *other, trace_t *trace) {
	int			team;
	gclient_t	*cl = other->client;

	if (!ent)
		return;

	if (!other->client)
		return;
	if (other->health < 1)
		return;		// dead people can't pickup

	// figure out what team this flag is
	if (ent->bots_team == TEAM_RED || strcmp(ent->classname, "bots_goal_red") == 0)
		team = TEAM_RED;
	else if (ent->bots_team == TEAM_BLUE || strcmp(ent->classname, "bots_goal_blue") == 0 )
		team = TEAM_BLUE;
	else {
		PrintMsg ( other, "Don't know what team the capture pad is for.\n");
		return;
	}

	if (team == cl->sess.sessionTeam) {
		Team_TouchOurFlag(ent, other, team, qtrue);
		return;
	}
	else {
		if (other->bots_class == BOTS_SOLDIER && other->client->qb_conquerMode) {
			// Conquer that pad
			ent->bots_flags |= 2; // Tag you're it

			BOTS_conquer_set(team, other->s.clientNum);
		}
	}
}


void bots_SpawnGoal( gentity_t *ent ) {
	qboolean	lb_goal;
	trace_t		tr;
	vec3_t		dest;
	
	lb_goal = qfalse;
	if ( (Q_stricmp (ent->classname, "bots_goal_blue") == 0) ||
	   (Q_stricmp (ent->classname, "bots_goal_red") == 0) ||
	   (Q_stricmp (ent->classname, "bots_goal_all") == 0) ) {

		ent->flags |= FL_NO_BOTS;
		ent->flags |= FL_NO_HUMANS;
		lb_goal = qtrue;
	}

	if (!lb_goal)
		return;
	else
		bots_setMode(CAPTURE_PADS);

	
	// BEGIN bots - tbp
	G_SpawnInt("bots_points", "2", &ent->bots_points);
	G_SpawnInt("bots_promos", "2", &ent->bots_promos);
	G_Printf("bots_SpawnGoal: team points %i, promo points %i\n",ent->bots_points,ent->bots_promos);
	// END bots - tbp
	
	ent->s.eType = ET_GENERAL;
	ent->clipmask = CONTENTS_SOLID;
	ent->s.number = ent - g_entities;
	ent->r.contents = CONTENTS_SOLID;//CONTENTS_TRIGGER;

	// Try to make the cap pad touch the floor	
	VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
	trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
	ent->s.groundEntityNum = tr.entityNum;
	G_SetOrigin( ent, tr.endpos );
	
	if (Q_stricmp (ent->classname, "bots_goal_blue") == 0) {
		ent->s.modelindex = G_ModelIndex("models/bots/capture_pad_blue.md3");
		ent->bots_team  = TEAM_BLUE;
	}
	else {
		ent->s.modelindex = G_ModelIndex("models/bots/capture_pad_red.md3");
		ent->bots_team  = TEAM_RED;
	}

	// set up cap pads so clients can walk on them
	VectorSet(ent->r.mins, -30, -30, 0);
	VectorSet(ent->r.maxs, 30, 30, 6);

	trap_LinkEntity (ent);

    if (Q_stricmp (ent->classname, "bots_goal_all") == 0) {
		ent->bots_flags = 1; // Horrible
	}
	else
		ent->bots_flags = 0;


	ent->think = bots_GoalThink;
	ent->nextthink = level.time + FRAMETIME;
	ent->touch = bots_GoalTouch;
}


// Routines to dynamically spawn entities based on a text file located
// in the /bots/maps/ directory. The ".ent" file should be named based
// on the name of the map. For example, "q3ctf1.ent" would dynamically
// load entities whenever the q3ctf1 map was loaded.


#define		MAX_ENT_TEXT	16384


char *G_AddSpawnVarToken( const char *string );
void G_SpawnGEntityFromSpawnVars( void );

void bots_G_ParseEnts( char *as_buffer ) {
	char	*ls_token;
	char	ls_key[MAX_TOKEN_CHARS];
	char	ls_info[MAX_INFO_STRING];

	while ( 1 ) {
		ls_token = COM_Parse( &as_buffer );
		if ( !ls_token[0] ) {
			break;
		}
		if ( strcmp( ls_token, "{" ) ) {
			Com_Printf( "Missing { in entity file\n" );
			break;
		}

//		G_LogPrintf( "BOTS: ----------- BEGIN ------------\n");

		level.numSpawnVars = 0;
		level.numSpawnVarChars = 0;

		ls_info[0] = '\0';
		while ( 1 ) {
			ls_token = COM_ParseExt( &as_buffer, qtrue );
			if ( !ls_token[0] ) {
				Com_Printf( "Unexpected end of entity file\n" );
				break;
			}
			if ( !strcmp( ls_token, "}" ) ) {
				break;
			}
			Q_strncpyz( ls_key, ls_token, sizeof( ls_key ) );

			ls_token = COM_ParseExt( &as_buffer, qfalse );
			if ( !ls_token[0] ) {
				strcpy( ls_token, "<NULL>" );
			}

			level.spawnVars[ level.numSpawnVars ][0] = G_AddSpawnVarToken( ls_key );
			level.spawnVars[ level.numSpawnVars ][1] = G_AddSpawnVarToken( ls_token );
			level.numSpawnVars++;

//			G_LogPrintf( "BOTS: Key: %s\n", ls_key);
//			G_LogPrintf( "BOTS: Token: %s\n", ls_token);
		}

		G_SpawnGEntityFromSpawnVars();
//		G_LogPrintf( "BOTS: ----------- END ------------\n");
	}

}



// This is called from the G_SpawnEntitiesFromString() function in the
// g_spawn.c file.

void bots_G_SpawnEntitiesFromString( void ) {
	int				li_length;
	char			ls_buffer[MAX_ENT_TEXT];
	char			ls_mapname[MAX_QPATH];
	char			ls_serverinfo[MAX_INFO_STRING];
	char			ls_filename[128];
	fileHandle_t	lfh_file;

	// Get the current map name
	trap_GetServerinfo( ls_serverinfo, sizeof(ls_serverinfo) );
	Q_strncpyz( ls_mapname, Info_ValueForKey( ls_serverinfo, "mapname" ), sizeof(ls_mapname) );

	// Build the filename
	ls_filename[0] = 0;
	Q_strcat( ls_filename, 128, "maps/" );
	Q_strcat( ls_filename, 128, ls_mapname );
	Q_strcat( ls_filename, 128, ".ent" );
	Q_strncpyz( ls_filename, Q_strlwr( ls_filename ), sizeof(ls_filename) );
	
	// Open the file if it exists
	li_length = trap_FS_FOpenFile( ls_filename, &lfh_file, FS_READ );
	if ( !lfh_file ) {
		G_LogPrintf( "BOTS: No custom map entities found in %s\n", ls_filename);
		return;
	}
	if ( li_length >= MAX_ENT_TEXT ) {
		G_LogPrintf( "BOTS: Custom map entities file too large: %s is %i, max allowed is %i\n", ls_filename, li_length, MAX_ENT_TEXT );
		trap_FS_FCloseFile( lfh_file );
		return;
	}

	// Read the entity data
	trap_FS_Read( ls_buffer, li_length, lfh_file );

	// Close the string off
	ls_buffer[li_length] = 0;

	// Parse and spawn the entities
	bots_G_ParseEnts( ls_buffer ) ;

	// Close the file
	trap_FS_FCloseFile( lfh_file );
}


qboolean bots_mapCheck(gentity_t *mapent, gentity_t *player)
{
	if (player->client)
	{
		if (mapent->bots_flagalarm) {
			if (player->client->keyindex) {
				// player has a flag
			} else {
				return qfalse;
			}
		}

		if (player->bots_class == BOTS_INFILTRATOR && 
			player->client->pers.classlevel > 2)
		{
			if ((mapent->bots_team == player->bots_team) && ((mapent->bots_class == BOTS_CAPTAIN) || (mapent->bots_class == BOTS_SCIENTIST)))
				return qfalse;
			else if (mapent->bots_team != player->bots_team) {
				if ((mapent->bots_class == BOTS_CAPTAIN) || (mapent->bots_class == BOTS_SCIENTIST)) {
					if (player->client->pers.classlevel == 3)
						return qfalse;
					else
						return qtrue;
				}
				else
					return qtrue;
			}
			else
				return qtrue;
		}
		
		if (mapent->bots_team) 
		{
			if ((player->bots_team != mapent->bots_team) && (mapent->bots_class == 1) && (player->bots_class == 1) && (player->client->pers.classlevel == 0))
				return qtrue;

			if (player->bots_team != mapent->bots_team)
				return qfalse;
			else
			{
				if ((mapent->bots_class) && (player->bots_class != mapent->bots_class))
					return qfalse;
			}
		}
		else
		{
			if ((mapent->bots_class) && (player->bots_class != mapent->bots_class))
				return qfalse;
		}
	}

	return qtrue;
}
